﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;

namespace PentaBiz.Studio.WpfTools.Behaviors
{
    public abstract class Behavior : DependencyObject
    {
        private FrameworkElement _associatedObject;

        public FrameworkElement AssociatedObject
        {
            get { return _associatedObject; }
            set
            {
                if (_associatedObject != null)
                {
                    OnDetaching();
                }
                _associatedObject = value;

                if (_associatedObject != null)
                {
                    OnAttached();
                }
            }
        }

        protected virtual void OnAttached()
        {
            AssociatedObject.Unloaded += AssociatedObjectUnloaded;
        }

        protected virtual void OnDetaching()
        {
            AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
        }

        private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
        {
            OnDetaching();
        }
    }

    public abstract class Behavior<T> : Behavior
        where T : FrameworkElement
    {
        public new T AssociatedObject
        {
            get { return (T) base.AssociatedObject; }
            set { base.AssociatedObject = value; }
        }
    }

    internal static class DesignMode
    {
        static DesignMode()
        {
            DesignModeEnabled =
                (bool) DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof (DependencyObject)).DefaultValue;
        }

        public static bool DesignModeEnabled { get; private set; }
    }

    /// <summary>
    ///     Attached dependency property storing 'behaviors'
    /// </summary>
    public static class Interaction
    {
        public static readonly DependencyProperty BehaviorsProperty =
            DependencyProperty.RegisterAttached("Behaviors",
                                                typeof (ObservableCollection<Behavior>),
                                                typeof (Interaction),
                                                new PropertyMetadata(
                                                    DesignMode.DesignModeEnabled
                                                        ? new ObservableCollection<Behavior>()
                                                        : null,
                                                    BehaviorsChanged));


        /// <summary>
        ///     Called when Property is retrieved
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static ObservableCollection<Behavior> GetBehaviors(DependencyObject obj)
        {
            var associatedObject = obj as FrameworkElement;
            var behaviors = obj.GetValue(BehaviorsProperty) as ObservableCollection<Behavior>;
            if (behaviors == null)
            {
                behaviors = new ObservableCollection<Behavior>();
                SetBehaviors(obj, behaviors);
            }

            return behaviors;
        }

        /// <summary>
        ///     Called when Property is retrieved
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="value"></param>
        public static void SetBehaviors(
            DependencyObject obj,
            ObservableCollection<Behavior> value)
        {
            obj.SetValue(BehaviorsProperty, value);
        }

        /// <summary>
        ///     Called when the property changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private static void BehaviorsChanged(
            object sender,
            DependencyPropertyChangedEventArgs args)
        {
            var associatedObject = sender as FrameworkElement;
            if (associatedObject != null)
            {
                var oldBehaviors = args.OldValue as ObservableCollection<Behavior>;
                if (oldBehaviors != null)
                {
                    foreach (Behavior behavior in oldBehaviors)
                    {
                        behavior.AssociatedObject = null;
                    }
                }

                var newBehaviors = args.NewValue as ObservableCollection<Behavior>;
                if (newBehaviors != null)
                {
                    foreach (Behavior behavior in newBehaviors)
                    {
                        behavior.AssociatedObject = sender as FrameworkElement;
                    }
                    newBehaviors.CollectionChanged += (collectionSender, collectionArgs) =>
                        {
                            switch (collectionArgs.Action)
                            {
                                case NotifyCollectionChangedAction.Add:
                                    {
                                        foreach (Behavior behavior in collectionArgs.NewItems)
                                        {
                                            behavior.AssociatedObject = associatedObject;
                                        }
                                        break;
                                    }
                                case NotifyCollectionChangedAction.Reset:
                                case NotifyCollectionChangedAction.Remove:
                                    {
                                        foreach (Behavior behavior in collectionArgs.NewItems)
                                        {
                                            behavior.AssociatedObject = null;
                                        }
                                        break;
                                    }
                            }
                        };
                }
            }
        }
    }
}